package org.bet.utils;

import java.lang.reflect.Method;
import java.util.Collection;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

public abstract class PredicateUtils extends org.apache.commons.collections.PredicateUtils {

	public static <T> Predicate equalPropertiesPredicate(T expectedTarget, String[] propertyNames){
		return new EqualPropertiesPredicate<T>(expectedTarget, propertyNames);
	}
	
	public static <T> Predicate equalCollectionPredicate(final Collection<T> targetColl, final String[] eltPropertyNames){
		if(eltPropertyNames == null){
			return new Predicate(){
				@Override
				public boolean evaluate(Object arg) {
					return CollectionUtils.isEqualCollection(targetColl, (Collection) arg);
				}
			};
		}
		
		return new Predicate(){
			@Override
			public boolean evaluate(Object arg) {
				Collection<T> actualColl = (Collection<T>) arg;
				if(CollectionUtils.size(targetColl) != CollectionUtils.size(actualColl)){
					return false;
				}
				
				if(actualColl == null || actualColl.size() == 0){
					return true;
				}
				for(final T targetElt : targetColl){
					T actualElt = (T) CollectionUtils.find(actualColl, new Predicate(){
						@Override
						public boolean evaluate(Object o) {
							return ObjectUtils.equals(targetElt, o);
						}
					});
					
					if(actualElt == null || !equalPropertiesPredicate(targetElt, eltPropertyNames).evaluate(actualElt)){
						return false;
					}
				}
				
				return true;				
			}
		};
	}
	
	public static class EqualPropertiesPredicate<T> implements Predicate {
		private final Log logger = LogFactory.getLog(getClass());
		
		protected String[] propertyNames = null;
		protected T expectedTarget = null;
		protected Method[] readMethods = null;
		
		public EqualPropertiesPredicate(T expectedTarget, String[] propertyNames) {
			Assert.notNull(expectedTarget, "expectedTarget should be not null");
			Assert.notEmpty(propertyNames, "propertyNames should be not emtpy");
			
			this.expectedTarget = expectedTarget;
			this.propertyNames = propertyNames;
			
			initReadMethods();
			
		}

		private void initReadMethods() {
			readMethods = new Method[propertyNames.length];
			for(int i = 0; i < propertyNames.length; i++){
//				Method method = ReflectionUtils.getReadMethod(ReflectionUtils.getParameterClassOf(getClass(), 0), propertyNames[i]);
				Method method = ReflectionUtils.getReadMethod(expectedTarget.getClass(), propertyNames[i]);
				if(method == null){
					throw new UnsupportedOperationException("Unsupported read method: get" + StringUtils.capitalise(propertyNames[i]));
				}
				readMethods[i] = method;
			}
		
		}

		@Override
		public boolean evaluate(Object arg) {
			T actual = (T) arg;
			try{
				for(Method readMethod : readMethods){
					Object expectedProp = readMethod.invoke(expectedTarget, new Object[]{});
					Object actualProp   = readMethod.invoke(actual, new Object[]{});
					
					if(!ObjectUtils.equals(expectedProp, actualProp)){
						return false;
					}
				}
				
				return true;
			}
			catch(Exception e){
				throw new RuntimeException(e);
			}
		}
	}
	
	
}
